﻿using System.Collections.Generic;
using Ah.Testudin.Engine.Actors;
using Ah.Testudin.Engine.Actors.Components;
using Ah.Testudin.Engine.Actors.Components.Render;
using Ah.Testudin.Engine.Actors.Spawning;
using Ah.Testudin.Engine.Content;
using Ah.Testudin.Engine.Events;
using Ah.Testudin.Engine.Graphics.Scenes;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Ah.Testudin.Engine.Processes
{
    public delegate ISceneNode GetSceneNodeDelegate(ActorId actorId);

    public class ExplosionProcess : ProcessBase
    {
        #region Private Member Variables

        private const float SCALE_MULTIPLIER = 1.3f;

        private const float INITIAL_SCALE = .1f;

        private readonly float _explosionRadius;

        private readonly float _force;

        private readonly GetAllActorsDelegate _getAllActorsDelegate;

        private readonly ISceneNode _explosionSceneNode;

        private readonly Actor _explodingActor;

        private readonly int _damage;

        private float _currentRadius;

        private Actor _explosionActor;

        private readonly IEventManager _eventManager;

        #endregion

        #region Constructors

        public ExplosionProcess(
            Actor explodingActor,
            float radius,
            float force,
            GetAllActorsDelegate getAllActorsDelegate,
            IActorSpawner actorSpawner,
            GetSceneNodeDelegate getSceneNodeDelegate,
            int damage,
            IEventManager eventManager)
        {
            _explodingActor = explodingActor;

            _explosionRadius = radius;

            _force = force;

            _getAllActorsDelegate = getAllActorsDelegate;

            Matrix transform = Matrix.CreateScale(INITIAL_SCALE);

            transform.Translation = explodingActor.GetComponent<TransformComponent>().Transform.Translation;

            _explosionActor = actorSpawner.SpawnActor(
                ActorType.Explosion,
                transform);

            _explosionSceneNode = getSceneNodeDelegate(_explosionActor.Id);

            _damage = damage;

            _currentRadius = INITIAL_SCALE / 2.0f;

            _eventManager = eventManager;
        }

        #endregion

        #region ProcessBase

        public override void Update(GameTime gameTime)
        {
            if (_currentRadius < _explosionRadius)
            {
                Matrix transform = _explosionSceneNode.Properties.ToWorldTransform;

                Vector3 translation = transform.Translation;

                transform *= Matrix.CreateScale(SCALE_MULTIPLIER);

                transform.Translation = translation;

                _explosionSceneNode.Properties.ToWorldTransform = transform;

                _currentRadius *= SCALE_MULTIPLIER;

                HandleAffectedActors();
            }
            else
            {
                _eventManager.QueueEvent(
                    new ActorDestroyedEvent(
                        _explosionActor.Id));

                _state = ProcessState.Succeeded;
            }
        }

        #endregion

        #region Private Methods

        private void HandleAffectedActors()
        {
            Vector3 explosionPosition = _explosionSceneNode.Properties.ToWorldTransform.Translation;

            IEnumerable<Actor> currentActors = _getAllActorsDelegate();

            foreach (Actor actor in currentActors)
            {
                if (actor.Id.Value != _explodingActor.Id.Value)
                {
                    TransformComponent transformComponent = actor.GetComponent<TransformComponent>();

                    if (transformComponent != null)
                    {
                        Vector3 actorPosition = transformComponent.Transform.Translation;

                        Vector3 difference = actorPosition - explosionPosition;

                        if (difference.Length() <= _currentRadius)
                        {
                            difference.Normalize();

                            // Apply Force 

                            PhysicsComponent physicsComponent = actor.GetComponent<PhysicsComponent>();

                            if (physicsComponent != null)
                            {
                                physicsComponent.ApplyForce(
                                    difference,
                                    _force);
                            }

                            // Apply Damage

                            HealthComponent healthComponent = actor.GetComponent<HealthComponent>();

                            if (healthComponent != null)
                            {
                                healthComponent.ApplyDamage(_damage);
                            }
                        }
                    }
                }
            }
        }

        #endregion
    }
}
